package cn.demoncat.util.web;

import cn.demoncat.util.exception.IoRuntimeException;
import cn.demoncat.util.io.IoUtil;
import cn.demoncat.util.io.entity.WeakServletOutputStream;
import cn.demoncat.util.lang.ParamEmptyUtil;
import cn.demoncat.util.lang.StringUtil;
import cn.demoncat.util.lang.constant.CharsetConstant;
import cn.demoncat.util.lang.constant.StringConstant;
import cn.demoncat.util.web.constant.HttpConstant;
import cn.demoncat.util.web.constant.MimeConstant;
import cn.demoncat.util.web.entity.RepeatableServletRequestWrapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * 描述： Request和Response等Servlet组件工具
 *
 * @author 延晓磊
 *
 * @since 2017年7月10日
 */
public final class ServletUtil {
	
	/**
	 * 重定向前缀
	 */
	public static final String REDIRECT = "redirect:";
	/**
	 * 转发前缀
	 */
	public static final String FORWARD = "forward:";
	/**
	 * 下载的Header1：内容处置说明
	 */
	private static final String DOWN_HEADER1 = "Content-Disposition";
	/**
	 * 下载的Header2：Content-Disposition = 下载附件; 文件名
	 */
	private static final String DOWN_HEADER2 = "attachment; filename=";
	/**
	 * 请求类型的Header键
	 */
	public static final String RT_HEADER = "X-Requested-With";
	/**
	 * Ajax请求类型
	 */
	public static final String AJAX_RT = "XMLHttpRequest";
	/**
	 * 来源头字段
	 */
	public static final String REFERER_HEADER = "Referer";

	
	/**
	 * 功能描述：获取Request
	 *
	 * request.getMethod()				> 请求方法 	> GET,POST
	 * request.getScheme()				> 请求协议 	> http,https
	 * request.getProtocol()			> 请求协议 	> HTTP/1.1
	 * request.getContentType()			> 请求类型	> application/json
	 * request.getServletPath()			> 请求路径 	> /user/list
	 * request.getRequestURI()			> 请求路径 	> /user/list
	 * request.getRequestURL()			> 请求路径	> http://demoncat.cn/user/list
	 * request.getCharacterEncoding()	> 请求编码	> UTF-8
	 * request.getHeader(name)			> 请求头
	 * request.getParameterMap()		> 请求参数（所有参数）
	 * request.getParameter(name)		> 请求参数（指定参数）
	 * request.getQueryString()			> 请求参数（查询参数）
	 * request.getLocalName()			> 本地HostName	> localhost
	 * request.getLocalAddr()			> 本地IP 		> 127.0.0.1
	 * request.getRemoteAddr()			> 远程IP		> 127.0.0.1
	 * request.getRemotePort()			> 远程端口		> 55238
	 *
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年6月13日
	 */
	public static HttpServletRequest getRequest() {
		RequestAttributes request = RequestContextHolder.getRequestAttributes();
		if (request == null) {
			return null;
		}
		return ((ServletRequestAttributes)request).getRequest();
	}
	
	/**
	 * 功能描述：获取Response
	 *
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年6月13日
	 */
	public static HttpServletResponse getResponse() {
		RequestAttributes request = RequestContextHolder.getRequestAttributes();
		if (request == null) {
			return null;
		}
		return ((ServletRequestAttributes)request).getResponse();
	}
	
	/**
	 * 功能描述：判断是否为AJAX请求
	 *
	 * @param request
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年4月27日
	 */
	public static boolean isAjax(HttpServletRequest request){
		//获取请求类型
		 String requestType = request.getHeader(RT_HEADER); 
		 if (StringUtils.isBlank(requestType)) {
			return false;
		}else{
			return requestType.equalsIgnoreCase(AJAX_RT);
		}
	}
	
	/**
	 * 获取响应输出流
	 * 
	 * @param response
	 * 
	 * @return WeakServletOutputStream：无法主动关闭的ServletOutputStream
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年6月4日
	 */
	public static ServletOutputStream getOut(HttpServletResponse response) {
		try {
			// 获取Out，通过包装类隐藏close方法，避免主动关闭response流造成异常情况
			return new WeakServletOutputStream(response.getOutputStream());
		} catch (IOException e) {
			throw new IoRuntimeException("获取Response输出流失败", e);
		}
	}
	
	/**
	 * 获取文件下载的OutputStream（匹配MIME类型）
	 * 
	 * <pre>
	 * 功能：设置响应头并获取Response.Out
	 * 适用：任意文件类型的下载（因为会匹配MIME类型，可能被浏览器解析）
	 * 注意：下载完成时，应返回null或void。否则SpringMvc将二次输出响应，并报错HttpMediaTypeNotAcceptableException
	 * </pre>
	 * 
	 * @param response
	 * @param fileName	文件名，将根据文件名称来判断MIME类型
	 * @return 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年4月27日
	 */
	public static ServletOutputStream getDownloadOut(HttpServletResponse response, String fileName) {
		ParamEmptyUtil.checkBlank(fileName);
		// 重置响应头
		response.reset();
		// 设置内容类型
		int index = fileName.lastIndexOf('.');
		if (index > 0) {
			// 匹配MIME类型
			response.setContentType(MimeConstant.get(fileName.substring(index + 1)));
		}else {
			// 通用的二进制文件下载
			response.setContentType(MimeConstant.DOWNLOAD);
		}
		// 设置附件名称
		response.setHeader(DOWN_HEADER1, DOWN_HEADER2 + StringUtil.utf2iso(fileName));
		// 获取Out
		return getOut(response);
	}
	
	/**
	 * 获取文件下载的OutputStream（不匹配MIME类型）
	 * 
	 * @param response
	 * @param fileName	文件名
	 * @return 
	 * 
	 * @see ServletUtil#getDownloadOut 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年4月27日
	 */
	public static ServletOutputStream getStreamOut(HttpServletResponse response, String fileName) {
		ParamEmptyUtil.checkBlank(fileName);
		// 重置响应头
		response.reset();
		// 设置文件类型：通用的二进制文件下载
		response.setContentType(MimeConstant.DOWNLOAD);
		// 设置附件名称
		response.setHeader(DOWN_HEADER1, DOWN_HEADER2 + StringUtil.utf2iso(fileName));
		// 获取Out
		return getOut(response);
	}
	
	/**
	 * 获取文件下载的OutputStream
	 * 
	 * @param response
	 * @param fileName	文件名
	 * @param type		是否匹配MIME类型，false将直接输出流文件
	 * @return 
	 * 
	 * @see ServletUtil#getDownloadOut 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年4月27日
	 */
	public static ServletOutputStream getDownloadOut(HttpServletResponse response, String fileName, boolean type) {
		if (type) {
			return getDownloadOut(response, fileName);
		}else {
			return getStreamOut(response, fileName);
		}
	}
	
	/**
	 * 获取图片输出的Out（设置页面不缓存）
	 * 
	 * @param response	
	 * 
	 * @author 延晓磊
	 * @return 
	 *
	 * @since 2017年4月27日
	 */
	public static ServletOutputStream getNocacheOut(HttpServletResponse response) {
		//重置响应头
		response.reset();
		//设置页面不缓存
		response.setHeader("Pragma", "no-cache");
		response.setHeader("Cache-Control", "no-cache");
		response.setDateHeader("Expires", 0);
		// 获取Out
		return getOut(response);
	}
	
	/**
	 * 功能描述：Response发送JSON文字信息
	 *
	 * @param response
	 * @param text
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年4月26日
	 */
	public static void response(HttpServletResponse response, String text){
		//设置响应头
		cors(response, null);
		response.setStatus(HttpStatus.OK.value());
		response.setContentType(MediaType.APPLICATION_JSON_VALUE); 
		response.setCharacterEncoding(CharsetConstant.UTF8);
		//发送信息
		responseText(response, text);
	}
	
	/**
	 * 功能描述：Response发送JSON文字信息
	 *
	 * @param text
	 * @param text
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年4月26日
	 */
	public static void response(String text){
		response(getResponse(),text);
	}
	
	/**
	 * Response发送文字响应
	 * 
	 * 注：Response流不需要关闭，由Tomcat自动关闭
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年11月27日
	 */
	public static void responseText(HttpServletResponse response, String text){
		try {
			PrintWriter writer = response.getWriter();
			writer.write(text);
			writer.flush();
		} catch (IOException e) {
			throw new IoRuntimeException("发送文本响应失败", e);
		}
	}
	
	/**
	 * Response发送文字响应
	 * 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年11月27日
	 */
	public static void responseText(String text){
		responseText(getResponse(),text);
	}
	
	/**
	 * 
	 * 功能描述：获取spring-mvc转发返回值
	 *
	 * @param url
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年4月26日
	 */
	public static String forward(String url){
		return FORWARD + url;
	}
	
	/**
	 * 
	 * 功能描述：转发
	 *
	 * @param url
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年4月26日
	 */
	public static void sendForward(HttpServletRequest request, HttpServletResponse response, String url){
		try {
			request.getRequestDispatcher(url).forward(request,response);
		} catch (ServletException | IOException e) {
			throw new IoRuntimeException("转发失败：url = "+url, e);
		}
	}
	
	/**
	 * 
	 * 功能描述：转发
	 *
	 * @param url
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年4月26日
	 */
	public static void sendForward(String url){
		sendForward(getRequest(),getResponse(), url);
	}
	
	/**
	 * 
	 * 功能描述：获取spring-mvc重定向返回值
	 *
	 * @param url
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年4月26日
	 */
	public static String redirect(String url){
		return REDIRECT + url;
	}
	
	/**
	 * 
	 * 功能描述：重定向
	 *
	 * @param url
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年4月26日
	 */
	public static void sendRedirect(HttpServletResponse response, String url){
		try {
			response.sendRedirect(url);
		} catch (IOException e) {
			throw new IoRuntimeException("重定向失败：url = "+url, e);
		}
	}
	
	/**
	 * 重定向
	 * 
	 * @param url
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月14日
	 */
	public static void sendRedirect(String url){
		sendRedirect(getResponse(), url);
	}
	
	/**
	 * 下载文件
	 * 
	 * @param bs
	 * @param fileName
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月14日
	 */
	public static void responseFile(byte[] bs, String fileName){
		responseFile(bs, fileName, getResponse());
	}
	
	/**
	 * 下载文件
	 * 
	 * @param bs
	 * @param fileName
	 * @param response
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月14日
	 */
	public static void responseFile(byte[] bs, String fileName, HttpServletResponse response){
		// 获取输出流
		ServletOutputStream output = getDownloadOut(response, fileName);
		try {
			// 输出
			output.write(bs);
			output.flush();
		} catch (IOException e) {
			throw new IoRuntimeException("发送文件响应失败", e);
		}
	}
	
	/**
	 * 跨域设置：如果已设置则忽略
	 * 
	 * @param origin	放行的域，* 或 null 表示全部
	 * @param response
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年7月5日
	 */
	public static void cors(HttpServletResponse response, String origin) {
		if(StringUtils.isBlank(response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)) ) {
			response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin == null ? StringConstant.STAR : origin);
		}
	}
	
	/**
	 * SSE响应头设置：用于Nginx反向代理时设置不缓存数据，以便长连接请求可以及时推送
	 * 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年7月6日
	 */
	public static void sse() {
		ServletUtil.getResponse().setHeader("X-Accel-Buffering", "no");
	}

	/**
	 * 获取request参数
	 *
	 * @param request
	 * @param param
	 *
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2020年3月27日
	 */
	public static String getParam(HttpServletRequest request, String param) {
		return request.getParameter(param);
	}

	/**
	 * 获取request参数
	 *
	 * @param param
	 *
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2020年3月27日
	 */
	public static String getParam(String param) {
		return getParam(getRequest(), param);
	}

	/**
	 * 获取request参数，转换为Map
	 * 
	 * @param request
	 * 
	 * @return Map，不处理空参数
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年3月27日
	 */
	public static Map<String, String> getParams(HttpServletRequest request) {
		Map<String, String> map = new HashMap<>();
		if (request.getParameterMap() != null) {
			for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
				if (entry.getValue() != null && entry.getValue().length > 0) {
					map.put(entry.getKey(), entry.getValue()[0]);
				}
			}
		}
		return map;
	}
	
	/**
	 * 获取request参数，转换为Map
	 * 
	 * @return Map，不处理空参数
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年3月27日
	 */
	public static Map<String, String> getParams() {
		return getParams(getRequest());
	}

	/**
	 * 获取JsonBody参数
	 *
	 * @param request	因为默认的HttpServletRequest只能读取1次InputStream，所以必须是RepeatableServletRequestWrapper
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2023年02月08日
	 */
	public static String getJsonBody(HttpServletRequest request){
		// 可重复读的Request + 类型为 application/json 的请求
		if (request instanceof RepeatableServletRequestWrapper && StringUtils.startsWith(request.getContentType(), MimeConstant.JSON)) {
			try {
				// 读取InputStream
				return IoUtil.readToStr(request.getInputStream());
			} catch (IOException e) {
				return null;
			}
		}
		return null;
	}

	/**
	 * 获取JsonBody参数
	 *
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2023年02月08日
	 */
	public static String getJsonBody(){
		return getJsonBody(getRequest());
	}

	/**
	 * 是否为POST请求
	 *
	 * @param request
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2021年07月14日
	 */
	public static boolean isPost(HttpServletRequest request){
		return HttpConstant.Method.POST.equals(request.getMethod());
	}

	/**
	 * 是否为multipart请求（上传文件）
	 *
	 * @param request
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2021年07月14日
	 */
	public static Boolean isMultipart(HttpServletRequest request) {
	 	return  request.getContentType().startsWith("multipart");
	}

	/**
	 *
	 * 取请求的Referer头(去除请求参数)
	 *
	 * @param request
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2017年6月8日
	 */
	public static String getReferer(HttpServletRequest request){
		return WebUtil.getReferer(request);
	}

	/**
	 * 获取Scheme
	 *
	 * @param request
	 * @return http,https
	 *
	 * @author 延晓磊
	 * @since 2021年07月14日
	 */
	public static String getScheme(HttpServletRequest request){
		return request.getScheme();
	}

	/**
	 * 获取所有属属
	 *
	 * @param request
	 * @return
	 */
	public static Map<String,Object> getAttributes(HttpServletRequest request){
		Map<String,Object> map = new HashMap<>();
		Enumeration<String> attributeNames = request.getAttributeNames();
		while (attributeNames.hasMoreElements()) {
			String attributeName = attributeNames.nextElement();
			Object attributeValue = request.getAttribute(attributeName);
			map.put(attributeName, attributeValue);
		}
		return map;
	}

	/**
	 * 获取请求路径
	 *
	 * @param request
	 * @return /api
	 */
	public static String getUri(HttpServletRequest request){
		return request.getRequestURI();
	}

	/**
	 * 获取完整请求路径
	 *
	 * @param request
	 * @return "http://localhost:8080/api
	 */
	public static String getUrl(HttpServletRequest request){
		return request.getRequestURL().toString();
	}

	/**
	 * 获取请求域名
	 *
	 * @param request
	 * @return localhost
	 */
	public static String getDomain(HttpServletRequest request){
		return request.getServerName();
	}

}
